using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Content;

namespace LabyrinthProject
{
    enum Action
    {
        Normal,
        Jumping,
        Falling,
        NoMove,
        NoJump,
        Freezed
    }

    class Player : Object, IDrawable
    {

        //enum koji služi za određivanje dal lik skače il se "normalno" kreće
        


        #region fields

        public  float minimumAltitude = 0f;
        private float freezeTime = 0.0f;
        public Vector3 offset = new Vector3(0, 0, 0);


        public GameTime gameTime;
        public BoundingBox[] randomBBox;
        public BoundingSphere[] randomBSphere;
        

        //public Vector3 movementVector = Vector3.One;

        /// <summary>
        /// A reference to the graphics device used to access the viewport for touch input.
        /// </summary>
        private GraphicsDevice graphicsDevice;

        private Model model;
        private Matrix world;
        private Vector3 up;
        private Vector3 right;
        private Vector3 position;
        private Vector3 direction;


        Keys keyUp;
        Keys keyDown;
        Keys keyLeft;
        Keys keyRight;
        Keys keyJump;

        Keys dKeyUp;  // default
        Keys dKeyDown;
        Keys dKeyLeft;
        Keys dKeyRight;

        //dodano by hrvpad -- varijable za resolvanje kolizije
        Vector3 oldPosition = new Vector3();
        Matrix oldWorldMatrix = new Matrix();
        
        Vector3 oldDirection = new Vector3();
        Vector3 oldUp = new Vector3();
        Vector3 oldRight = new Vector3();


        private BoundingSphere boundingSphere;

        SpriteBatch sprite;
        private Texture2D whiteTexture;


        /// <summary>
        /// Full speed at which ship can rotate; measured in radians per second.
        /// </summary>
        private const float rotationRate = 1.5f;

        /// <summary>
        /// Mass of ship.
        /// </summary>
        private const float mass = 1.0f;

        /// <summary>
        /// Maximum force that can be applied along the ship's direction.
        /// </summary>
        //private const float moveStrenght = 24000.0f;
        private const float moveStrenght = 18000.0f;
        //private const float thrustForce = 24000.0f * 0.7f;

        /// <summary>
        /// Velocity scalar to approximate drag.
        /// </summary>
        private const float dragFactor = 0.92f;

        /// <summary>
        /// Current ship velocity.
        /// </summary>
        public Vector3 velocity;

        private Matrix scaleMatrix = Matrix.CreateScale(0.5f);
        private Matrix rotacija;

        private List<PowerUp> powerUpList;


        //dodano by hrpadjen -- skok
        Action bodyAction = Action.Normal;
        private const int MAX_JUMP_AMOUNT = 450;
        Vector3 sumOfAddedForces;
        Vector3 sumOfInitialForces = new Vector3(0, -9000, 0);

        public bool cookiedPickedUp; 

        #endregion

        #region properties

        public Matrix ScaleMatrix
        {
            get { return scaleMatrix; }
            set { scaleMatrix = value; }
        }

        public Matrix Rotacija
        {
            get { return rotacija; }
            set { rotacija = value; }
        }

        public Action BodyAction
        {
            get { return bodyAction; }
            set { bodyAction = value; }
        }

        public float MinimumAltitude
        {
            get { return minimumAltitude; }
        }

        public Model Model
        {
            get
            {
                return model;
            }
            set
            {
                model = value;
            }
        }

        public List<PowerUp> PowerUpList
        {
            get { return powerUpList; }
            set { powerUpList = value; }
        }

        public BoundingSphere BoundingSphere
        {
            get
            {
                return boundingSphere;
            }
            set { boundingSphere = value; }
        }

        public Vector3 Right
        {
            get
            {
                return right;
            }
            set { right = value; }
        }

        public Vector3 Up
        {
            get
            {
                return up;
            }
            set { up = value; }
        }

        public Matrix World
        {
            get
            {
                return world;
            }
            set { world = value; }
        }

        public Vector3 OldRight
        {
            get { return oldRight; }
            set { oldRight = value; }
        }

        public Vector3 OldUp
        {
            get { return oldUp; }
            set { oldUp = value; }
        }

        public Vector3 OldDirection
        {
            get { return oldDirection; }
            set { oldDirection = value; }
        }

        public Matrix OldWorldMatrix
        {
            get { return oldWorldMatrix; }
            set { oldWorldMatrix = value; }
        }

        public Vector3 OldPosition
        {
            get { return oldPosition; }
            set { oldPosition = value; }
        }

        public Vector3 Position
        {
            get { return position; }
            set { position = value; }
        }

        public Vector3 Direction
        {
            get { return direction; }
            set { direction = value; }
        }

        #endregion

        #region initialization

        public Player()
        {
        }

        public Player(ContentManager content, GraphicsDevice device, string modelName, Vector3 position, 
                      Keys keyUp, Keys keyDown, Keys keyLeft, Keys keyRight, Keys keyJump)
        {
            this.graphicsDevice = device;

            this.model = content.Load<Model>(modelName);
            this.powerUpList = new List<PowerUp>();

            this.position = new Vector3(position.X, minimumAltitude, position.Z);
            this.direction = Vector3.Right;
            this.up = Vector3.Up;
            this.right = Vector3.Right;
            this.velocity = Vector3.Zero;

            this.keyUp = keyUp;
            this.keyDown = keyDown;
            this.keyLeft = keyLeft;
            this.keyRight = keyRight;
            this.keyJump = keyJump;

            this.dKeyUp = keyUp;
            this.dKeyDown = keyDown;
            this.dKeyLeft = keyLeft;
            this.dKeyRight = keyRight;

            oldPosition = this.position;
            oldUp = up;
            oldRight = right;
            oldDirection = direction;

            rotacija = Matrix.Identity;

            this.sprite = new SpriteBatch(LabyrinthGame.GDevice);
            whiteTexture = content.Load<Texture2D>("Textures/BacteriaColor");
        }

        /// <summary>
        /// Restore the ship to its original starting state
        /// </summary>
        public void Reset()
        {
            position = new Vector3(0, minimumAltitude, 0);
            direction = Vector3.Forward;
            up = Vector3.Up;
            right = Vector3.Right;
            velocity = Vector3.Zero;
        }


        #endregion

        #region methods

        public void ChangeModel(ContentManager content, string model, float scaleFactor, Vector3 positionOffset)
        {
            this.model = content.Load<Model>(model);
            this.ScaleMatrix = Matrix.CreateScale(scaleFactor);
        }

        /// <summary>
        /// Applies a simple rotation to the ship and animates position based
        /// on simple linear motion physics.
        /// </summary>
        public void Update(GameTime gameTime, Camera cam, Player secondPlayer, List<AI> ai)
        {
            this.gameTime = gameTime;
            //randomBBox = new BoundingBox[ai.Count];

            randomBSphere = new BoundingSphere[ai.Count];

            if (bodyAction != Action.Freezed)
            {
                MakeAMove(gameTime);
                CollisionHandle(CollisionDetection.CheckCollision(this, secondPlayer, ai));
            }

            else
            {
                //ako je iznad dozvoljene visine, prijeđi u stanje padanja
                if (position.Y >= minimumAltitude)
                    bodyAction = Action.Falling;


                float gravityAmount = 1.7f;
                if (bodyAction == Action.Falling)
                {
                    gravityAmount = 1.7f;
                }

                Vector3 force = Vector3.Zero;
                force += (sumOfInitialForces * gravityAmount);

                // Apply acceleration
                Vector3 acceleration = force / mass;

                velocity += acceleration * (float)gameTime.ElapsedGameTime.TotalSeconds;

                velocity.X = 0.0f;
                velocity.Z = 0.0f;

                // Apply velocity
                position += velocity * (float)gameTime.ElapsedGameTime.TotalSeconds;

                // Prevent ship from flying under the ground
                position.Y = Math.Max(position.Y, minimumAltitude);

                transformWorldMatrix();

                bodyAction = Action.Freezed;

                if (freezeTime < 5.0f)
                {
                    freezeTime += (float)gameTime.ElapsedGameTime.TotalSeconds;
                }
                else
                {
                    freezeTime = 0.0f;
                    bodyAction = Action.Normal;
                }
            }

            UpdatePowerUps(gameTime);
        }

        private void transformWorldMatrix()
        {

            // Reconstruct the ship's world matrix
            world = Matrix.Identity;
            world.Forward = direction;
            world.Up = up;
            world.Right = right;
            world.Translation = position;

            Matrix rotacija = Matrix.CreateRotationY(MathHelper.ToRadians(-90)) * Matrix.CreateRotationY(MathHelper.Pi);
            Matrix rotacija2 = Matrix.CreateRotationY(MathHelper.ToRadians(-90)) * Matrix.CreateRotationY(MathHelper.Pi);


            //world = rotacija2 * world;
            world = rotacija * world;
           
            world = rotacija2 * world;
            
            world = scaleMatrix * world;

            //World = rotacija * World;
        }

                
        
        public void MakeAMove(GameTime gameTime) //pomiče playera/lika
        {
            KeyboardState keyboardState = Keyboard.GetState();
            float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;

            float moveDirectionAmount = 1f; // postitive infinity indicates no change
            Vector3 jumpSpeed = new Vector3(0, 10000, 0);

            CheckPowerUpStatus(ref moveDirectionAmount, ref jumpSpeed, gameTime);


            float rotationAmount = 0;
            if (keyboardState.IsKeyDown(keyLeft))
                rotationAmount = 1.5f;
            if (keyboardState.IsKeyDown(keyRight))
                rotationAmount = -1.5f;

            #region rotation
            // Scale rotation amount to radians per second
            rotationAmount = rotationAmount * rotationRate * elapsed;

            // Create rotation matrix from rotation amount
            Matrix rotationMatrix = Matrix.CreateRotationY(rotationAmount);

            // Rotate orientation vectors
            direction = Vector3.TransformNormal(direction, rotationMatrix);
            up = Vector3.TransformNormal(up, rotationMatrix);

            // Re-normalize orientation vectors
            // Without this, the matrix transformations may introduce small rounding
            // errors which add up over time and could destabilize the ship.
            direction.Normalize();
            up.Normalize();

            // Re-calculate Right
            right = Vector3.Cross(direction, up);

            // The same instability may cause the 3 orientation vectors may
            // also diverge. Either the Up or Direction vector needs to be
            // re-computed with a cross product to ensure orthagonality
            up = Vector3.Cross(right, direction);
            #endregion

            #region jump
            //ako je stisnuta tipka za skok, skoči
            float jumpAmount = 0;
            if (keyboardState.IsKeyDown(keyJump) && ((bodyAction == Action.Normal) || (bodyAction == Action.Jumping)))
            {
                ClearForces();
                AddForce(jumpSpeed);
                jumpAmount = 3.5f;
                //gravityAmount = 1.0f;
            }

            //ako je iznad dozvoljene visine, prijeđi u stanje padanja
            if (position.Y >= minimumAltitude + MAX_JUMP_AMOUNT)
                bodyAction = Action.Falling;


            float gravityAmount = 1.7f;
            if (bodyAction == Action.Falling)
            {
                gravityAmount = 1.7f;
            }

            #endregion

            #region movement forward and back

            // Determine thrust amount from input
            if (moveDirectionAmount != 0) // if no power effect happened
            {
                if (keyboardState.IsKeyDown(keyUp) && (bodyAction == Action.Normal))
                    moveDirectionAmount *= 1.0f;
                else if (keyboardState.IsKeyDown(keyUp) && ((bodyAction == Action.Jumping) || (bodyAction == Action.Falling)))
                    moveDirectionAmount *= 0.4f;
                else if (keyboardState.IsKeyDown(keyDown) && (bodyAction == Action.Normal))
                    moveDirectionAmount *= -1.0f;
                else
                    moveDirectionAmount = 0;
            }

            // Calculate force from thrust amount
            Vector3 force = direction * moveDirectionAmount * moveStrenght;

            force += (sumOfAddedForces * jumpAmount + sumOfInitialForces * gravityAmount);

            // Apply acceleration
            Vector3 acceleration = force / mass;

            velocity += acceleration * elapsed;

            // Apply psuedo drag
            if (acceleration != Vector3.Zero)
            {
                velocity *= dragFactor;
            }
            else
            {
                velocity *= 0.75f;
            }

            // Apply velocity
            position += velocity * elapsed;

            // Prevent ship from flying under the ground
            position.Y = Math.Max(position.Y, minimumAltitude);

            //vrati se u normal stanje ako je lik/tijelo "na zemlji"
            if (position.Y == (minimumAltitude))
                bodyAction = Action.Normal;

            #endregion

            transformWorldMatrix();
        }
        
        public void AddForce(Vector3 force) //funkcija koja dodaje sile koje djeluju na tijelo/igrača
        {
            sumOfAddedForces += force;
        }

        private void ClearForces() //funkcija koja "čisti" sile koje djeluju na tijelo/igrača
        {
            sumOfAddedForces = Vector3.Zero;
            //sumOfInitialForces = Vector3.Zero;
            //torque = Vector3.Zero;
        }

        private void CollisionHandle(List<CollisionType> playerColType)
        {
            bool hasWallCollision = false;
            bool hasPlayerCollision = false;
            bool hasAiCollision = false;

            foreach (CollisionType collisionType in playerColType)
            {
                switch (collisionType)
                {
                    case CollisionType.Wall:
                        hasWallCollision = true;
                        break;
                    case CollisionType.Player:
                        hasPlayerCollision = true;
                        break;
                    case CollisionType.None:
                        break;
                    case CollisionType.AI:
                        hasAiCollision = true;
                        break;
                    default:
                        break;
                }

                if (hasWallCollision)
                {
                    CollisionDetection.ResolveWallCollision(this);
                }

                if (hasPlayerCollision)
                {
                    CollisionDetection.ResolvePlayer2PlayerCollision(this);
                    
                }

                if (hasAiCollision)
                {
                    CollisionDetection.ResolvePlayerAICollision(this);
                }
            }
            
            

            oldWorldMatrix = world;
            oldPosition = position;
            oldDirection = direction;
            oldUp = up;
            oldRight = right;
            hasWallCollision = false;
        }

        #region powerUp methods

        internal void AddPowerUp(PowerUp newPowerUp)
        {
            bool contains = false;

            foreach (PowerUp powerUp in powerUpList)
            {
                if (powerUp.TypeOfPowerUp == newPowerUp.TypeOfPowerUp)
                {
                    powerUp.LifeTime = newPowerUp.LifeTime;
                    contains = true;
                    break;
                }
            }

            if (!contains)
            {
                powerUpList.Add(newPowerUp);
            }
        }

        private void UpdatePowerUps(GameTime gameTime)
        {
            float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;
            PowerUp deletePowerUp = null;
            foreach (PowerUp powerUp in powerUpList)
            {
                powerUp.LifeTime -= elapsed;

                if (powerUp.LifeTime <= 0) deletePowerUp = powerUp;
            }

            if (deletePowerUp != null) powerUpList.Remove(deletePowerUp);
        }

        bool screwUpbool = true;
        private void CheckPowerUpStatus(ref float moveDirectionAmount, ref Vector3 jumpSpeed, GameTime gameTime) // movement powerUps
        {
            bool nBool = true;
            foreach (PowerUp powerUp in powerUpList)
            {
                if (powerUp.TypeOfPowerUp == PowerUpType.Cipela)
                {
                    moveDirectionAmount = 0;
                    this.bodyAction = Action.NoMove;
                }
                if (powerUp.TypeOfPowerUp == PowerUpType.Skok)
                {
                    jumpSpeed = Vector3.Zero;
                }
                if (powerUp.TypeOfPowerUp == PowerUpType.Upitnik)
                {
                    if (screwUpbool)
                    {
                        ScrewUpControls();
                        screwUpbool = false;
                    }
                    nBool = false;
                }
                if (powerUp.TypeOfPowerUp == PowerUpType.Zarulja)
                {
                }
            }


            if (nBool)
            {
                NormalizeControls();
                screwUpbool = true;
            }

        }
        private bool CheckPowerUpStatus()
        {
            foreach (PowerUp powerUp in powerUpList)
            {
                if (powerUp.TypeOfPowerUp == PowerUpType.Zarulja)
                    return true;
            }
            return false;
        }
        
        public void LightsOut(Viewport viewport)
        {
            if (CheckPowerUpStatus())            
            {
                LabyrinthGame.GDevice.Viewport = viewport;
                LabyrinthGame.GDevice.BlendState = BlendState.AlphaBlend;

                Rectangle rec = new Rectangle(0, 0, viewport.Width, viewport.Height);
                sprite.Begin(SpriteSortMode.Texture, BlendState.AlphaBlend);
                sprite.Draw(whiteTexture, rec, null, new Color(new Vector4(0, 0, 0, 0.98f)), 0, new Vector2(0, 0), SpriteEffects.None, 0);
                sprite.End();
            }
        }

        private void ScrewUpControls()
        {
            Random random = new Random();
            Keys nKey;
            if (random.NextDouble() >= 0.5f)
            {
                nKey = keyUp;
                keyUp = keyDown;
                keyDown = nKey;
            }
            if (random.NextDouble() >= 0.5f)
            {
                nKey = keyRight;
                keyRight = keyLeft;
                keyLeft = nKey;
            }
            if (random.NextDouble() >= 0.5f)
            {
                nKey = keyUp;
                keyUp = keyRight;
                keyRight = nKey;
            }
            if (random.NextDouble() >= 0.5f)
            {
                nKey = keyDown;
                keyDown = keyLeft;
                keyLeft = nKey;
            }
        }
        private void NormalizeControls()
        {
            this.keyUp = this.dKeyUp;
            this.keyDown = this.dKeyDown;
            this.keyLeft = this.dKeyLeft;
            this.keyRight = this.dKeyRight;
        }

        #endregion


        public void Draw(Camera camera)
        {
            graphicsDevice.Viewport = camera.Viewport;

            Matrix[] transforms = new Matrix[model.Bones.Count];
            model.CopyAbsoluteBoneTransformsTo(transforms);


            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
                    effect.World = transforms[mesh.ParentBone.Index] * world;

                    // Use the matrices provided by the chase camera
                    effect.View = camera.View;
                    effect.Projection = camera.Projection;
                }
                mesh.Draw();
            }

            //BoundingSphereRenderer.Render(boundingSphere, graphicsDevice, camera.View, camera.Projection, new Color(1, 0, 0));
            //if (randomBBox != null)
            //{
            //for (int i = 0; i < randomBSphere.Length; i++)
            //{
            //    //BoundingSphereRenderer.Render(BoundingSphere.CreateFromBoundingBox(randomBBox[i]), graphicsDevice, camera.View, camera.Projection, new Color(1, 0, 0));
            //    BoundingSphereRenderer.Render(randomBSphere[i], graphicsDevice, camera.View, camera.Projection, new Color(1, 0, 0));
            //}
                
            //}
        }

        #endregion
 
    }
}
